系統概要與應用情境
🎯 系統目的
本系統同時讀取兩個影像來源(示範影片 + WebCam),透過 YOLOv8 偵測人物、EuclideanDistTracker 追蹤 ID,計算即時人數、累積 ID、峰值、短期平均、長期平均與趨勢,適用於抗議現場、集會活動雙來源比對分析。
📡 預設來源設定
群眾來源 A:videos/protest_demo.mp4
若影片不存在自動切換至 WebCam 0
群眾來源 B:本地 WebCam 0
若攝影機占用則自動切換至影片來源
🖥️ 畫面佈局
上方左右各顯示群眾 A、B,各自附資訊面板顯示人數、ID、峰值、平均值與狀態,下方為雙折線圖即時比較兩路人流變化趨勢。
⚙️ 核心技術
YOLOv8n — 人物偵測 conf=0.30
EuclideanDistTracker — threshold 45px
Pillow (PIL) — 中文文字繪製
deque(maxlen=120) — 滑動視窗
檔案架構說明
app.py — 主程式
讀取 config → 建立兩個 CrowdMonitor → 載入 YOLO → 主迴圈同步處理雙路影像 → 拼合畫面並顯示。包含 CrowdStats、CrowdMonitor dataclass、自動備援切換、中文繪圖函式、折線圖生成。
tracker.py — 追蹤器
EuclideanDistTracker 使用質心歐氏距離匹配上一幀的物件 ID,threshold=45px,超出範圍視為新物件分配新 ID,同幀結束後清除非活躍 ID。
crowd_config.json — 系統設定
定義模型路徑、解析度、信心值、history_size,以及兩個來源的名稱、主要來源、備援清單與顯示顏色(accent)。
系統架構圖與資料流
圖一:整體資料流(從影像輸入到畫面輸出)
圖二:雙來源並行架構
程式流程與安裝步驟
安裝步驟
切換到工作目錄
cd C:\AI_Traffic_System60401_1安裝所需套件
pip install -r .. equirements.txt將群眾影片放入
20260401_1ideos\protest_demo.mp4執行主程式
python app.py按 q 或 Esc 結束程式
主要套件
ultralytics
YOLOv8 模型推論,輸出 bounding box xyxy 座標
opencv-python
VideoCapture 讀取影像、resize、imshow、polylines
Pillow (PIL)
ImageFont + ImageDraw 繪製中文文字至影像上
numpy
np.full 建立背景陣列、np.hstack/vstack 拼合畫面
程式流程說明
讀取設定:load_config() 讀取 crowd_config.json,解析 sources 陣列建立 2 個 CrowdMonitor,每個含主要來源與備援清單。
開啟影像來源:CrowdMonitor.open() 依序嘗試 source_candidates,第一個成功的成為 active_source。
逐幀推論:process_frame() 讀取一幀 → resize 640×360 → YOLOv8 predict(classes=[0]) → 取出 xyxy 轉換為 (x,y,w,h)。
ID 追蹤:EuclideanDistTracker.update() 計算質心距離,<45px 沿用舊 ID,否則分配新 ID 並累計 seen_ids。
統計分析:analyze_crowd() 計算短期平均(10幀)、長期平均(30幀)、classify_trend() 比較最近 6 幀與前 6 幀差值判定趨勢。
畫面組合:annotate_frame() 畫 bounding box → draw_panel() 貼資訊面板 → hstack 並排 A、B → build_chart() 折線圖 → vstack 合成完整 dashboard。
def update(self, objects_rect): objects_bbs_ids = [] for rect in objects_rect: x, y, w, h = rect cx = (x + x + w) // 2 # 計算質心 cy = (y + y + h) // 2 same_object_detected = False for object_id, point in self.center_points.items(): distance = math.hypot(cx - point[0], cy - point[1]) if distance < self.distance_threshold: self.center_points[object_id] = (cx, cy) objects_bbs_ids.append([x, y, w, h, object_id]) same_object_detected = True break if not same_object_detected: self.center_points[self.id_count] = (cx, cy) objects_bbs_ids.append([x, y, w, h, self.id_count]) self.id_count += 1 return objects_bbs_ids
def classify_trend(history): if len(history) < 12: return "資料累積中" recent = history[-6:] previous = history[-12:-6] delta = mean(recent) - mean(previous) if delta >= 6: return "快速上升" elif delta >= 2: return "緩步上升" elif delta <= -6: return "快速下降" elif delta <= -2: return "緩步下降" else: return "大致穩定"
核心技術與功能說明
YOLOv8 人物偵測
使用 yolov8n.pt 輕量模型,只偵測 class 0(人物),confidence ≥ 0.30,輸出 bounding box (x1,y1,x2,y2)。
質心距離 ID 追蹤
EuclideanDistTracker 計算每幀質心與上幀的歐氏距離,<45px 沿用舊 ID,同一人只計算一次。
趨勢判斷
比較最近 6 幀與前 6 幀平均差值 δ,分為快速上升 / 緩步上升 / 大致穩定 / 緩步下降 / 快速下降。
雙折線圖比較
build_chart() 同時繪製兩路即時人數折線與 10 幀移動平均線,視覺化比較雙來源人流變化。
自動備援切換
reopen_next_source() 在主要來源失敗時依序嘗試 fallback_sources,確保系統不因來源中斷而停止。
Pillow 中文繪圖
使用 msjh.ttc / msyh.ttc 字型,透過 PIL ImageDraw 將中文資訊直接繪製到 NumPy 影像陣列上。
| 指標名稱 | 說明 | 計算方式 |
|---|---|---|
| 目前人數 | 當幀追蹤到的活躍 ID 數量 | len(tracked) |
| 累積辨識 ID | 曾出現過的唯一人物總數 | len(seen_ids) |
| 峰值人數 | 歷史最高單幀人數 | max(peak, current) |
| 短期平均 | 最近 10 幀的平均人數 | mean(history[-10:]) |
| 長期平均 | 最近 30 幀的平均人數 | mean(history[-30:]) |
| 趨勢判讀 | 6 幀差值 δ 分 5 級判斷 | classify_trend() |
實作過程截圖
以下為本次實作過程中的畫面截圖紀錄。
AI 輔助開發記錄
Claude (claude.ai) — 系統設計與程式生成
輸入需求:「建立雙視訊人流分析系統,使用 YOLOv8 + EuclideanDistTracker,同時處理兩路影像並顯示趨勢折線圖」
Gemini — 除錯與套件問題諮詢
諮詢 Pillow 中文字型路徑設定、cv2.VideoCapture 備援切換邏輯、YOLOv8 classes 參數過濾人物偵測。
問題排除紀錄
| # | 問題描述 | 原因分析 | 解決方法 | 狀態 |
|---|---|---|---|---|
| 01 | protest_demo.mp4 無法開啟 | 影片未放入 videos/ 資料夾 | 確認路徑,系統自動切換備援來源(WebCam 0) | 已解決 |
| 02 | 中文文字顯示亂碼 | OpenCV putText 不支援中文 | 改用 Pillow ImageFont + ImageDraw 繪製中文字 | 已解決 |
| 03 | 雙來源其中一路無畫面 | WebCam 被其他軟體占用 | 關閉占用攝影機的程式後重啟,或調整 source 編號 | 已解決 |
| 04 | 人數數值波動過大 | YOLOv8 confidence 設定太低 | 將 confidence 從 0.25 調高至 0.30,搭配折線圖平均線觀察 | 已解決 |
| 05 | 同一人被分配多個 ID | distance_threshold 太小 | 將 EuclideanDistTracker threshold 從 30 調整為 45px | 已解決 |
| 06 | 趨勢一直顯示「資料累積中」 | history 未達 12 幀門檻 | 等待約 12 幀後自動更新;可降低門檻至 8 加速初始化 | 觀察中 |
學習心得與總結
本週最大的收穫是理解了「多來源並行處理」的架構設計思維。以往只處理單一影像,這次需要同時維護兩個 CrowdMonitor 的狀態、各自的歷史 deque、追蹤器與統計資料,讓我深刻體會到 dataclass 在管理複雜狀態時的優雅性。
EuclideanDistTracker 的設計非常直觀——質心距離 < 45px 就視為同一人。雖然簡單,但在固定場景下效果出乎意料地穩定。相比 Deep SORT 等複雜方法,它的計算量極低,非常適合 IoT 邊緣裝置部署。
自動備援切換(reopen_next_source)是這次系統最實用的工程設計。影片播完、攝影機斷線都能無縫切換,這種「防禦性設計」思維在工業 IoT 場景中非常重要。
使用 AI 工具(Claude)輔助生成程式骨架後,我花最多時間在「理解與調整」:為什麼 threshold 設 45 而不是 30?為什麼 short_avg 取 10 幀?這些數字背後的邏輯才是真正的學習。AI 給了答案,但理解答案的過程才讓知識真正內化。